//===========================================================================
//  LIB INCLUDES
//===========================================================================
#include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//===========================================================================
//  HEADER INCLUDES
//===========================================================================

#include "types.h"

//===========================================================================
//  RESOURCES INCLUDES
//===========================================================================

#include "polyhedrons.c"

//===========================================================================
//  GLOBAL VARIABLES
//===========================================================================

FP			currentSum = 0;
FP			currentError = 0;
U32			nbGeneratedCubes = 0;
U32			nbComputatedCubes = 0;
POINT*		polyhedron = NULL;
U32			polyhedronType = 0;
U32			polyhedronNbFacets = 0;
FP_POINT*	equations = NULL;
double		recursiveTime = 0.0;
double		computeTime = 0.0;
SETTINGS	settings;
S64			calls = 0;


// Cube Array Management (Master node)
void*		cubeStock = NULL;
U32			stockCubeSize = 0;
U32			writeIndex = 0;
U32			readIndex = 0;

// Working data (Maggot node)
U32			maggotCubeSize;



//===========================================================================
//  SRC INCLUDES
//===========================================================================

#include "fp.c"
#include "equations.c"
#include "stock.c"
#include "initialize.c"
#include "compute.c"
#include "cubes.c"
#include "ui.c"


//===========================================================================
//  MAIN PROGRAM
//===========================================================================

int main(int argc, char** argv)
{
	S32 myid;
	S32 numProcs;

	MPI_Init(&argc,&argv); // MPI Initialization
	MPI_Comm_rank(MPI_COMM_WORLD,&myid); // Getting current processID
	MPI_Comm_size(MPI_COMM_WORLD,&numProcs);

	if(setvbuf(stdout, NULL, _IONBF, 0))
	{
        perror("failed to change the buffer of stdout");
        return EXIT_FAILURE;
    }


	if(numProcs<2) //Not enough nodes, stopping computation
	{
		printf("Sorry Master, not enough maggots available.\n");
		return(1);
	}

	//--------------------------------------------------
	// MASTER NODE
	//--------------------------------------------------

	if(myid==0) //Master Node
	{
		// 1. Ask the user for options
		//----------------------------

		U32 preComputationDepth;
		chooser(&preComputationDepth);

		// 2. Inform the other nodes of the parameters
		//--------------------------------------------

		printf("Summoning maggots...             ");
		MPI_Bcast(&settings, sizeof(SETTINGS),MPI_BYTE,0,MPI_COMM_WORLD);
		printf("Done.\n\n");



		// 3. Pre-compute the working sub cubes
		//--------------------------------------
		printf("Pre-computing to rank #%d...      ",preComputationDepth);

		//Initializing Polyhedron & Stock
		usePolyhedron(settings.polyhedronType);
		useComputationMethod(settings.computationMethod,70000);

		//Build a list of jobs
		switch(settings.computationMethod)
		{
		case METHOD_MEMORY:
			runRecursiveWithMemory(preComputationDepth);
			printf("Done.\n");
			break;

		case METHOD_NOMEMORY:
			runRecursiveNoMemory(preComputationDepth);
			printf("Done.\n");
			break;
		}
		printf("%d cubes ready for maggots.\n\n",nbGeneratedCubes);

		// 4. Send jobs to nodes
		//----------------------

		//Creating termination cube
		void* terminationCube = calloc(1,stockCubeSize);
		S32 terminatedProcesses=0;

		//Send jobs to available nodes
		printf("Sending jobs to maggots...       ");
		for(S32 i = 1; i< numProcs ;i++)
		{
			if(STOCK_isEmpty())
			{
				MPI_Send(terminationCube,stockCubeSize,MPI_BYTE,i,0,MPI_COMM_WORLD);
				terminatedProcesses++;
			}
			else
			{
				MPI_Send(STOCK_readNext(),stockCubeSize,MPI_BYTE,i,0,MPI_COMM_WORLD);
				STOCK_ReadIncrement();
			}
		}
		printf("Done.\n\n");

		printf("Computing...\n");
		printf("|----------------------------------------------------------------------------------------------------|\n|");



		// 5. Wait for their response
		//---------------------------

		MAGGOTRESPONSE	response;
		MPI_Status		status;
		double*			computationTimes = (double*) calloc(numProcs,sizeof(double));
		U32				processedCubes=0;
		U32				progressBarIndex=0;
		double			initialTime = MPI_Wtime();

		while(terminatedProcesses < numProcs-1)
		{
			MPI_Recv(&response,sizeof(MAGGOTRESPONSE),MPI_BYTE,MPI_ANY_SOURCE,0,MPI_COMM_WORLD,&status);
			currentSum += response.sum;
			currentError += response.error;
			computationTimes[status.MPI_SOURCE] += response.computationTime;
			calls += response.calls;
			processedCubes++;
			
			// Print progress bar segments
			U32 progressIndex = (processedCubes * 100)/nbGeneratedCubes;
			while (progressIndex > progressBarIndex)
			{
				printf("=");
				progressBarIndex++;
			}


			if(STOCK_isEmpty())
			{
				MPI_Send(terminationCube,stockCubeSize,MPI_BYTE,status.MPI_SOURCE,0,MPI_COMM_WORLD);
				terminatedProcesses++;
			}
			else
			{
				MPI_Send(STOCK_readNext(),stockCubeSize,MPI_BYTE,status.MPI_SOURCE,0,MPI_COMM_WORLD);
				STOCK_ReadIncrement();
			}
		}
		double computationTime = MPI_Wtime() - initialTime;

		printf("|\n|----------------------------------------------------------------------------------------------------|\n\n");

		printResults(preComputationDepth,computationTime,computationTimes,numProcs-1,processedCubes);
		logResults(preComputationDepth,computationTime,computationTimes,numProcs-1,processedCubes);

		//printf("Job done Master.\n");
		//printf("%d maggots died at your service\n",numProcs-1);



#ifdef WIN32
		system("pause");
#endif

	}
	
	//--------------------------------------------------
	// MAGGOT NODE
	//--------------------------------------------------
	
	else 
	{
		//Waiting for settings
		MPI_Bcast(&settings,sizeof(SETTINGS),MPI_BYTE,0,MPI_COMM_WORLD);

		//Initialization
		usePolyhedron(settings.polyhedronType);
		useComputationMethod(settings.computationMethod,1);
		MAGGOTRESPONSE response;

		//Listen for jobs from the root node
		while(42)
		{
			resetVolumeData();
			MPI_Status status;
			void* buffer = STOCK_writeNext();
			MPI_Recv(buffer,stockCubeSize,MPI_BYTE,0,0,MPI_COMM_WORLD,&status);
			double time;

			if( ( *((FP*)buffer) )==0 )
			{
				break; //Termination cube was received, exiting.
			}
			
			//Get it done
			time = MPI_Wtime();
			SIMPLE_CUBE* cube;
			switch(settings.computationMethod)
			{
			case METHOD_MEMORY:
				recursiveTestWithMemory((CUBE*) STOCK_readNext(),settings.maximumRecursion);
				break;

			case METHOD_NOMEMORY:
				cube = (SIMPLE_CUBE*) STOCK_readNext();
				recursiveTestNoMemory(cube->p.x,cube->p.y,cube->p.z,cube->a,settings.maximumRecursion);
				break;
			}

			//Report to root node when done
			response.computationTime = MPI_Wtime() - time;
			response.error = currentError;
			response.sum = currentSum;
			response.calls = calls;
			MPI_Send(&response,sizeof(MAGGOTRESPONSE),MPI_BYTE,0,0,MPI_COMM_WORLD);
		}
		
		
	}
	MPI_Finalize(); // MPI Finalization
	return 0;
}